#ifndef __PROCESS_POOL_HPP
#define __PROCESS_POOL_HPP

#include<iostream>
#include<sys/types.h>
#include<unistd.h>
#include<cstdlib>
#include<string>
#include<vector>
#include<functional>
#include<sys/wait.h>
#include<ctime>
#include"Task.hpp"
const int gdefault_process_num=5;

using callback_t =std::function<void(int fd)>;


class Channel
{
public:
    Channel() {}
    Channel(int fd, const std::string &name, pid_t id)
        : _wfd(fd), _name(name), _sub_target(id)
    {
    }
    void DebugPrint()
    {
        printf("channel name:%s,wfd:%d,target pid:%d\n", _name.c_str(), _wfd, _sub_target);
    }
    pid_t Target()
    {
        return _sub_target;
    }
    ~Channel() {}
    int Fd()
    {
        return _wfd;
    }
    std::string Name() { return _name; }
    void Close(){ close(_wfd); }
    void Wait()
    {
        //等待目标子进程
        pid_t rid=waitpid(_sub_target,nullptr,0);
        (void)rid;
    }

private:
    int _wfd;//管道的文件描述符
    std::string _name;//管道的名字（channel+第几个子进程）
    pid_t _sub_target;//目标子进程的id
};

class ProcessPool
{
public:
    ProcessPool(int num = gdefault_process_num)
        : _num(num)
    {
        srand(time(nullptr)^getpid()^0x777);
    }

    ~ProcessPool() {}

    bool InitProcessPool(callback_t cb)
    {
        //创建管道并创建子进程。
        for (int i = 0; i < _num; i++)
        {
            // 创建管道
            int pipefd[2] = {0};
            int n = pipe(pipefd);
            (void)n;

            // 创建子进程
            pid_t id = fork();
            if (id == 0)
            {
                //
                for(auto &c:_channels)
                {
                    c.Close();
                }

                // 关闭不需要的读写端
                close(pipefd[1]);

                //子进程做事情
                //当创建好子进程之后，调用回调函数
                cb(pipefd[0]);

                //当读取不到内容或读取内容有问题时，就会break跳出循环，然后结束进程。
                //在这里不存在读取任务码出错的情况，因为每次都是四个字节写，每次写了四个字节就会立刻读走四个字节，
                exit(0);
            }
            //父进程关闭读端
            close(pipefd[0]);
            std::string name = "Channel-" + std::to_string(i);
            //记录管道名称
            _channels.emplace_back(pipefd[1], name, id);
            // Channel chan(pipefd[1],name,id);
            // channels.push_back(chan);
        }
        return true;
    }

    void PollingCtrlSubProcess()
    {
        int index = 0;
        while (true)
        {
            int who = index;
            index++;
            index %= _channels.size();
            //选择任务
            int x = rand()%tasks.size();
            std::cout << "选择信道：" << _channels[who].Name() << ",target:" << _channels[who].Target() << std::endl;
            write(_channels[who].Fd(), &x, sizeof(x));
            sleep(1);
        }
    }

    void PollingCtrlSubProcess(int count)
    {
        if(count<0) return;
        int index = 0;
        while (count)
        {
            int who = index;
            index++;
            index %= _channels.size();
            //选择任务
            int x = rand()%tasks.size();
            std::cout << "选择信道：" << _channels[who].Name() << ",target:" << _channels[who].Target() << std::endl;
            //写也是四个字节的写，写入任务列表中任务的下标
            write(_channels[who].Fd(), &x, sizeof(x));
            sleep(1);
            count--;
        }
    }

    void WaitSubProcess()
    {
        //因为管道关闭后，子进程肯定读不到内容了，read返回值为0，这时候就会跳出循环结束子进程。所以说关闭管道文件相当于结束了子进程
        for(int end=_channels.size()-1;end>=0;end--)
        {
            //关闭管道，结束子进程
            _channels[end].Close();
            //这个wait是由父进程来调用的
            //当管道文件被关闭后，子进程读不到东西了就会跳出循环结束。可是如果写端不写，写端不关，子进程就会一直堵塞，也就是说，如果我们正着关，会减少第一个进程的引用计数
            //父进程对于这个管道文件的引用计数没了，但是其他四个子进程仍然链接着写端。此时，就会陷入子进程堵塞的模式。第一子进程不会关闭
            //所以说，要想关闭所有子进程，要么一次性都关了所有的管道，再去调用wait，要么就倒着循环，因为最后一个管道的写端只有一个。
            _channels[end].Wait();
        }
    }
    
private:
    std::vector<Channel> _channels; // 所有信道
    int _num;                       // 子进程个数
};

#endif